import os
import sys
import time
import redis
import logging
import simplejson
# import pygraphviz

from base.context import Context
from database.rdb_server import RedisServer
from configure.env_config import EnvConfig
from utils.common_utils import *
from base.build_graph import BuildGraph
from utils.db_object_pool import DBObjectPool
from base.build_organizer import BuildOrganizer
from configure.config_logging import main_log_init
from utils.options_parser import ArgParser
from base.build_complete import BuildComplete


class IRGen(object):
    
    def __init__(self):
        self.context = Context()
        self.argparser = ArgParser()
        self.capture_db_server = RedisServer()
        self.filecache_db_server = RedisServer()
        self.env = {}
        self.build_graph = BuildGraph()
        self.build_organizer = BuildOrganizer()
    
    def init_context(self):
        """ initial the context of runtime """
        self.context.update_work_path(os.getcwd())

    def init_logger(self):
        main_log_init(log_dir=self.context.LOG_DIR, log_mode='a', log_name=self.context.LOG_NAME, log_level='DEBUG', enable_print=self.context.ENABLE_LOG_PRINT)
        
    def clear_work_dirs(self):
        """ clear previous word directories """
        if self.context.CLEAR_WORK_DIRS:
            if os.path.exists(self.context.IRGEN_WORK_PATH):
                try:
                    shutil.rmtree(self.context.IRGEN_WORK_PATH)
                except Exception as e:
                    print(e)

    def make_work_dirs(self):
        """ make IRGEN work dirs"""
        if not os.path.exists(self.context.IRGEN_WORK_PATH):
            os.mkdir(self.context.IRGEN_WORK_PATH)
        
        if not os.path.exists(self.context.IR_DIR):
            os.mkdir(self.context.IR_DIR)
            
        if not os.path.exists(self.context.MAPLE_DIR):
            os.mkdir(self.context.MAPLE_DIR)
            
        if not os.path.exists(self.context.AST_DIR):
            os.mkdir(self.context.AST_DIR)
            
        if not os.path.exists(self.context.DB_DIR):
            os.mkdir(self.context.DB_DIR)
        
        if not os.path.exists(self.context.LOG_DIR):
            os.mkdir(self.context.LOG_DIR)
        
        if not os.path.exists(self.context.CACHE_DIR):
            os.mkdir(self.context.CACHE_DIR)
        
        if not os.path.exists(self.context.ERROR_DIR):
            os.mkdir(self.context.ERROR_DIR)
            
        if not os.path.exists(self.context.MID_TEMP_DIR):
            os.mkdir(self.context.MID_TEMP_DIR)
            
        if not os.path.exists(self.context.BUILD_GRAPH_DIR):
            os.mkdir(self.context.BUILD_GRAPH_DIR)
        
        if not os.path.exists(self.context.REPORT_DIR):
            os.mkdir(self.context.REPORT_DIR)
        
        if not os.path.exists(self.context.OBJ_DIR):
            os.mkdir(self.context.OBJ_DIR)
        
        if not os.path.exists(self.context.ASSEMBLE_DIR):
            os.mkdir(self.context.ASSEMBLE_DIR)
        
        if not os.path.exists(self.context.TOP_DIR):
            os.mkdir(self.context.TOP_DIR)
    
    def init_runtime_environ(self):
        """ initial the runtime environment """
        self.env = dict(os.environ)
        EnvConfig.set_environ(self.context)
        EnvConfig.update_runtime_env(self.env)
        
    def starts_redis_server(self):
        """ starts capture and filecache redis server """
        
        self.context.check_redis_cached_config()
        self.capture_db_server.config_redis_server(redis_server=self.context.REDIS_SERVER,\
                                                    name=self.context.REDIS_CAPTURE_DB_NAME,\
                                                    db_dir=self.context.DB_DIR,\
                                                    unix_socket=self.context.REDIS_CAPTURE_DB_SOCKET,\
                                                    port="0")
        
        self.filecache_db_server.config_redis_server(redis_server=self.context.REDIS_SERVER,\
                                                    name=self.context.REDIS_CACHE_DB_NAME,\
                                                    db_dir=self.context.DB_DIR,\
                                                    unix_socket=self.context.REDIS_CACHE_DB_SOCKET,\
                                                    port="0")
        self.capture_db_server.start_redis_server()
        self.filecache_db_server.start_redis_server()
    
    def shutdown_redis_server(self):
        """ shutdown capture and filecache redis server """
        cap_db_cli = redis.Redis(unix_socket_path=self.context.REDIS_CAPTURE_DB_SOCKET, port=0)
        cap_db_cli.shutdown()
        
        cache_db_cli = redis.Redis(unix_socket_path=self.context.REDIS_CACHE_DB_SOCKET, port=0)
        cache_db_cli.shutdown()
        
    def build_capture(self, command=[]):
        if self.context.IRBUILD_ONLY:
            return 0
        
        if not command:
            raise RuntimeError("build command")
        
        ret = 1
        try:
            EnvConfig.active_capture_env(env=self.env)
            ret = run_command(command=command, env=self.env)
        except Exception as e:
            print(e)
        return ret

    def generate_ir(self):
        
        if self.context.CAPTURE_ONLY:
            return 
        
        self.build_graph.construct_build_graph()
        if self.context.ENABLE_BUILD_GRAPH_DUMP:
            dump_file = os.path.join(self.context.BUILD_GRAPH_DIR, self.context.BUILD_GRAPH_DUMP)
            status = self.build_graph.graph_dump(dump_file=dump_file)
            if status:
                logging.info("Dump build graph in %s" % dump_file)
            else:
                logging.exception("Dump build graph failed")
            
        self.build_organizer.init_build_env()
        ret = self.build_organizer.irbuild()
        self.build_organizer.release_build_env()
        
        return ret
        
    def dump_md5_map(self):
        gcc_name_dep = DBObjectPool.get_db_inst_by_name("gcc").get_name_dep()
        clang_name_dep = DBObjectPool.get_db_inst_by_name("clang").get_name_dep()
        ar_name_dep = DBObjectPool.get_db_inst_by_name("ar").get_name_dep()
        ld_name_dep = DBObjectPool.get_db_inst_by_name("ld").get_name_dep()
        
        all_name_dep = {
            "gcc_name_dep": gcc_name_dep,
            "clang_name_dep": clang_name_dep,
            "ar_name_dep": ar_name_dep,
            "ld_name_dep": ld_name_dep
            }
        
        with open(self.context.NAME_DEP, 'w') as f:
            simplejson.dump(all_name_dep, f, indent=4)
    
    
    def run(self):
        self.argparser.parse_args()
        color_print("MapleIRGen: a tool for automatically generating whole program maple")
        time_start = time.time()
        color_print("[INFO] Initialing Runtime enviroment configure")
        self.init_context()
        self.clear_work_dirs()
        self.make_work_dirs()
        self.init_logger()
        self.init_runtime_environ()
        color_print("[INFO] Starting Redis Compile Information Database Server")
        self.starts_redis_server()
        build_command = self.argparser.get_build_args()
        color_print("[INFO] Starting Capture Origianl Building Process")
        ret = self.build_capture(command=build_command)
        if ret:
            color_print("[ERROR] Test Project Build Failed, Please Check Build Configuration")
        else:
            color_print("[INFO] Capturing Origianl Building Process Done")
            color_print("[INFO] Starting Generate Whole Program Maple IR")
            ret = self.generate_ir()
            color_print("[INFO] Generating Whole Program Maple IR Done")
        
        if not ret and self.context.ENABLE_WHOLE_PROCESS:
            # need to compelet the whole build process
            color_print("[INFO] Complete Whole Building")
            bc = BuildComplete(self.context)
            bc.complete_build()
            
        self.dump_md5_map()
        self.shutdown_redis_server()
        color_print("[INFO] Redis Compile Information Database Server Closed")
        color_print("[INFO] EXIT")
        return 0 